[Amazon Lex] Lex-SDKでセッション情報や永続化情報が簡単に扱えるようになりました
1 はじめに
AIソリューション部の平内(SIN)です。
Amazon Lex(以下、Lex)では、 validation及び、FulfillmentでLambdaファンクションを呼び出すことが可能であり、きめ細かいスロットのバリデーションや、インテント完了時の処理を定義することができます。
Lexの仕組みは、インテント、スロット、ダイアログモードなど、その骨格はAmazon Alexa(以下、Alexa)に非常に似ていますが、Alexa SDKのような環境がないため、既に、Alexaの開発に慣れている方にも、Lex開発は、ちょっと戸惑うというのが現状かも知れません。
本記事で紹介する、Lex SDKは、この問題を解決するため、Alexa SDKと同じようなスタイルでLex開発することを目標としたSDKであり、JavaScript及び、TypeScriptで利用可能です。
https://github.com/furuya02/lex-sdk
今回は、昨日、このLex SDKに追加された、セッション情報や、永続化情報を簡単に扱うことができるAttributesManagerクラスの利用方法について紹介させて頂きます。
2 AttributesManagerクラス
AttributesManagerは、アトリビュートの管理のためのクラスであり、下記の3種類のデータを扱うことが出来ます。
- リクエストアトリビュート(1リクエストのライフサイクルでのみ存続)
- セッションアトリビュート(スキルセッションが継続している間に存続)
- 永続アトリビュート(セッションのライフサイクルを終了しても存続)
利用可能なメソッドなメソッドは以下の7つです。
// リクエストアトリビュート用 getRequestAttributes() : {[key : string] : any}; setRequestAttributes(requestAttributes : {[key : string] : any}) : void; // セッションアトリビュート用 getSessionAttributes() : {[key : string] : any}; setSessionAttributes(sessionAttributes : {[key : string] : any}) : void; // 永続アトリビュート用 getPersistentAttributes() : Promise<{[key : string] : any}; setPersistentAttributes(persistentAttributes : {[key : string] : any}) : void; savePersistentAttributes() : Promise<void>;
AttributesManagerは、Lex.HandlerInputのプロパティとして定義されており、必要な情報(アトリビュー)を取得(若しくは、保存)に利用することが出来ます。
下記は、SampleIntentのハンドラで、Lex.HandlerInput経由で取得したAttributesManager使用して、セッション情報を取得している例です。
const SampleIntentHandler: Lex.RequestHandler = { canHandle(h: Lex.HandlerInput) { return (h.intentName == 'SampleIntent') }, async handle(h: Lex.HandlerInput) { // セッション情報の取得 let attributes = await h.attributesManager.getPersistentAttributes();
3 動作確認用ボット
特に何もないテスト用のボットで動作確認していきます。テスト用のボットは、以下のようになっています。
- インテントは、SmpleIntentのみ
- サンプル発話は「Hello」のみ
- スロットなし
- validation及び、FulfillmentにLambda(LexSmaple)を設定
4 リクエストアトリビュート
最初に、1回のリクエストのライフサイクルでのみ存続するデータであるリクエストデータを使うサンプルです。
リクエストデータは、getRequestAttributes()で取得し、setRequestAttributes()で保存できます。
ハンドラ間でデータのやり取りができていることを確認するために、すべてのハンドラの前に処理を挿入できるaddRequestInterceptors()で追加したハンドラ(requestInspectorHandler)でデータを設定し、それを別のハンドラ(SampleIntentHandler)で使用しています。
import * as Lex from 'lex-sdk'; let bot: Lex.Bot; exports.handler = async function (event: Lex.IntentRequest, context: any) { console.log(JSON.stringify(event)); if (!bot) { bot = Lex.BotBuilder() .addRequestHandlers(SampleIntentHandler) .addRequestInterceptors(requestInspectorHandler) .create(); } return bot.invoke(event, context); } const requestInspectorHandler: Lex.RequestInterceptor = { process(h: Lex.HandlerInput) { // リクエスト情報の設定 let attributes = h.attributesManager.getRequestAttributes(); attributes.speak = 'test'; // リクエストデータとして、speakを格納する h.attributesManager.setRequestAttributes(attributes); } } const SampleIntentHandler: Lex.RequestHandler = { canHandle(h: Lex.HandlerInput) { return (h.intentName == 'SampleIntent') }, async handle(h: Lex.HandlerInput) { // リクエスト情報の取得 const attributes = h.attributesManager.getRequestAttributes(); const speak = attributes.speak; // リクエストデータのspeakを取り出す if (h.source === Lex.InvocationSource.DialogCodeHook) { return h.responseBuilder .getDelegateResponse(h.slots) } else { // FulfillmentCodeHook const message = { contentType: Lex.ContentType.PlainText, content: speak}; return h.responseBuilder .getCloseResponse( Lex.FulfillmentState.Fulfilled, message) } } }
動作は、以下のようになっています。
5 セッションアトリビュート
リクエストアトリビュートは、あまり使う場面が無いかも知れませんが、セッションデータは、比較的利用頻度が高いのではないでしょうか。
リクエストデータは、getSessionAttributes()で取得し、setSessionAttributes()で保存できます。
setSessionAttributes()で設定されたアトリビュートは、ResponseBuilderがレスポンスを生成する際に、自動的にSttributeに追加されます。
import * as Lex from 'lex-sdk'; let bot: Lex.Bot; exports.handler = async function (event: Lex.IntentRequest, context: any) { console.log(JSON.stringify(event)); if (!bot) { bot = Lex.BotBuilder() .addRequestHandlers( SampleIntentHandler) .create(); } return bot.invoke(event, context); } const SampleIntentHandler: Lex.RequestHandler = { canHandle(h: Lex.HandlerInput) { return (h.intentName == 'SampleIntent') }, async handle(h: Lex.HandlerInput) { if (h.source === Lex.InvocationSource.DialogCodeHook) { return h.responseBuilder .getDelegateResponse(h.slots) } else { // FulfillmentCodeHook // リクエスト情報の取得 const attributes = h.attributesManager.getSessionAttributes(); if(attributes.counter == undefined) { attributes.counter = 0; } attributes.counter = Number(attributes.counter) + 1; h.attributesManager.setSessionAttributes(attributes); let speak = `カウンターは、${attributes.counter}です。` const message = { contentType: Lex.ContentType.PlainText, content: speak}; return h.responseBuilder .getCloseResponse( Lex.FulfillmentState.Fulfilled, message) } } }
上記のコードを実行した結果です。コンソール上でのテストでは、Clear chat historyを押すまでは、同一セッションとして動作するため、カウンターがインクリメントされている様子が確認できます。
6 永続アトリビュート
最後に永続アトリビュートです。セッションに関係なく、ボットとして永続的にデータを保持したい場合に利用できます。
永続データは、getPersistentAttributes()で取得し、setPersistentAttributes()で保存でき、savePersistentAttributes()で永続化(DB上書き)されます。setPersistentAttributes()では、まだ、DBへ書き込まれていないことにご注意下さい。
(1) テーブル定義
なお、永続アトリビュートを使用する場合、.withTableName()による、DynamoDBのテーブルの定義が必要です。.withTableName()の第1パラメータは、テーブル名であり、第2引数は、主キーに格納するデータです。
Alexa SDKの場合、主キーに格納する値は、デフォルトでUserIdが利用され、各ユーザーごとのデータが保持される仕組みとなっていますが、Lexでは、ユーザーを識別するという概念自体が存在しないため、ここでは、値を明示的に指定して、ボットに固有のデータとして保存する仕様としています。
※ 実は、LexにのUserIdがあるのですが、この値は、クライアント側アプリが自由に設定できるため、ユーザーの識別に利用できる保証はありあせん。
exports.handler = async function (event: Lex.IntentRequest, context: any) { console.log(JSON.stringify(event)); if (!bot) { bot = Lex.BotBuilder() .addRequestHandlers(SampleIntentHandler) .withTableName("tableName", "TestBot") // テーブル名指定 .withAutoCreateTable(true) //テーブル作成もスキルから行う .create(); } return bot.invoke(event, context); }
(2) 利用例
永続データを保存・取得しているサンプルです。
import * as Lex from 'lex-sdk'; let bot: Lex.Bot; exports.handler = async function (event: Lex.IntentRequest, context: any) { console.log(JSON.stringify(event)); if (!bot) { bot = Lex.BotBuilder() .addRequestHandlers( SampleIntentHandler) .withTableName("SampleBotTable", "SampleBot") // テーブル名指定 .withAutoCreateTable(true) //テーブル作成もスキルから行う .create(); } return bot.invoke(event, context); } const SampleIntentHandler: Lex.RequestHandler = { canHandle(h: Lex.HandlerInput) { return (h.intentName == 'SampleIntent') }, async handle(h: Lex.HandlerInput) { if (h.source === Lex.InvocationSource.DialogCodeHook) { return h.responseBuilder .getDelegateResponse(h.slots) } else { // FulfillmentCodeHook // 永続情報の取得 const attributes = await h.attributesManager.getPersistentAttributes(); if(attributes.counter == undefined) { attributes.counter = 0; } attributes.counter = Number(attributes.counter) + 1; // 永続情報の保存 h.attributesManager.setPersistentAttributes(attributes); await h.attributesManager.savePersistentAttributes(); let speak = `カウンターは、${attributes.counter}です。` const message = { contentType: Lex.ContentType.PlainText, content: speak}; return h.responseBuilder .getCloseResponse( Lex.FulfillmentState.Fulfilled, message) } } }
実行すると、下記のようになります。 Clear chat historyを押して、セッションを新たに開始しても、カウンターは初期化されません。
DynamoDBでは、永続データが保存されていることを確認できます。
(3) DynamoDBの定義
Lsx SDKでDynamoDBを使用すると、デフォルトで、Lambdaの配置されたリージョンのDynamoDBが利用されます。 これを、変更したい場合や、ローカルの試験中に別のパーミッションで動作させたい場合などは、DynamoDBオブジェクトを自前で定義することも可能です。
import * as Lex from './lex-sdk'; const AWS = require('aws-sdk'); AWS.config.credentials = new AWS.SharedIniFileCredentials({profile: 'my-profile'}); let bot: Lex.Bot; exports.handler = async function (event: Lex.IntentRequest, context: any) { console.log(JSON.stringify(event)); if (!bot) { bot = Lex.BotBuilder() .addRequestHandlers(SampleIntentHandler) .withTableName("SampleBotTable", "SampleBot") // テーブル名指定 .withAutoCreateTable(true) //テーブル作成もスキルから行う .withDynamoDbClient( new AWS.DynamoDB({ apiVersion: "latest", region: "ap-northeast-1" }) ) .addErrorHandlers(ErrorHandler) .create(); } return bot.invoke(event, context); }
7 最後に
今回は、セッション情報や、永続化情報を簡単に扱うことができるAttributesManagerクラスの利用方法について紹介させて頂きました。
Lex SDKは、まだまだ、雑な実装ですが、逐次進化できるように頑張りたいと思います。
弊社ではAmazon Connectのキャンペーンを行なっております。
4月に引き続き、5月も「無料Amazon Connectハンズオンセミナー」を開催予定です。導入を検討されておられる方は、是非、お申し込み下さい。
また音声を中心とした各種ソリューションの開発支援も行なっております。